package com.hzchendou.blog.demo.auth.config;

import com.hzchendou.blog.demo.auth.security.AuthAuthenticationEntryPoint;
import com.hzchendou.blog.demo.auth.security.AuthClientCredentialsTokenEndpointFilter;
import com.hzchendou.blog.demo.auth.security.granter.SmsCodeTokenGranter;
import com.hzchendou.blog.demo.auth.service.AuthClientDetailService;
import com.hzchendou.blog.demo.auth.service.PhoneSmsCodeService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

/**
 * @Date: 2022-05-31 22:33
 * @since: 1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthClientDetailService authClientDetailService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PhoneSmsCodeService phoneSmsCodeService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Autowired
    private TokenEnhancer tokenEnhancer;


    /**
     * 配置客户端信息
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(authClientDetailService);
    }


    /**
     * 配置OAuth token相关配置
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                .authenticationManager(authenticationManager)/// 用于密码模式验证时需要提供客户端身份验证信息
                // refresh_token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
                //      1.重复使用：access_token过期刷新时， refresh token过期时间未改变，仍以初次生成的时间为准
                //      2.非重复使用：access_token过期刷新时， refresh_token过期时间延续，在refresh_token有效期内刷新而无需失效再次登录
                .reuseRefreshTokens(false);
        //// 取出系统中的四模式
        List<TokenGranter> granters = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
        /// 添加手机验证码的授权模式
        granters.add(new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), phoneSmsCodeService));
        /// 这是一个组装模式，实现了TokenGranter接口，循环调用List中的TokenGranter组件进行校验处理，直到返回验证成功信息或者是异常信息
        CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granters);
        endpoints.tokenGranter(compositeTokenGranter);
        endpoints.tokenServices(tokenService());//令牌管理服务
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.authenticationEntryPoint(new AuthAuthenticationEntryPoint());
        security.tokenKeyAccess("permitAll()")//oauth/token_key是公开
                .checkTokenAccess("permitAll()");//oauth/check_token公开
        AuthClientCredentialsTokenEndpointFilter endpointFilter = new AuthClientCredentialsTokenEndpointFilter(security);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(new AuthAuthenticationEntryPoint());
        // 客户端认证之前的过滤器
        security.addTokenEndpointAuthenticationFilter(endpointFilter);
    }


    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service = new DefaultTokenServices();
        service.setClientDetailsService(authClientDetailService);
        service.setSupportRefreshToken(true);
        service.setTokenStore(tokenStore);
        //令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer);
        tokenEnhancers.add(accessTokenConverter);
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        service.setTokenEnhancer(tokenEnhancerChain);
        service.setAccessTokenValiditySeconds(60 * 60 * 2); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); // 刷新令牌默认有效期3天
        return service;
    }
}
